package com.phoenix.jsef.common;


import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import java.io.*;
import java.util.*;


/**
* @author 李启联 E-mail:liql@gillion.com.cn
* @version 创建时间：2017年9月16日 上午01:37:09
* 类说明:处理xml文档工具类
*/
public class XmlUtils {

	public  XmlUtils() {}
	
	/**
	 * 此方法为convertXmlToMapList的一子方法【已过时】
	 * 该方法实现在xml文档中取出指定节点的单个循环节点下的map转换，并支持递归
	 * 此方法设计的巧妙之处在于把xml转为Map形式，这样以来转为java中的实体类就简单多了【毕竟在java中System.out.println打印出来的对象就是Map形式】
	 * 友情推荐：在xml文档内容不大时使用该方法是一个不错的选择
	 * @param xmlStr xml 文档内容
	 * @param xPathExpress xPath 表达式  如果为空则从根节点开始遍历
	 * @param ignoreAttr 是否忽略对属性的解析
	 * @return Map<String,Object> 对象
	 * @throws Exception
	 */
	@Deprecated
	public static Map<String,Object> convertXmlSingleNodeToMap(String xmlStr,String xPathExpress,Boolean ignoreAttr) throws Exception{
		
		Map nodeDataMap = new LinkedHashMap<>();
		String subElementXml ="",elementName="",sameElemXml ="",subXPathExpress ="",compareName="";
		
		Document doc = new SAXReader().read(new ByteArrayInputStream(xmlStr.getBytes("UTF-8")));		
		if("".equalsIgnoreCase(xPathExpress) || null == xPathExpress)//如果表达式 为空则从根节点开始遍历
		{
			 Element  root = doc.getRootElement();//获取根节点
			 xPathExpress = root.getName();
		}
		Element element = (Element) doc.selectSingleNode(xPathExpress);
		
		//得到当前节点里的属性
		List<Attribute> elementAttrs = element.attributes();
		//得到当前节点下的子节点集合
		List<Element> sonElements = element.elements();
		
		if(!ignoreAttr) {
			//遍历当前节点里的属性
			Iterator iteratoreAttrs = elementAttrs.iterator();
			while(iteratoreAttrs.hasNext()) {
				Attribute attr = (Attribute) iteratoreAttrs.next();
				nodeDataMap.put(attr.getName(),attr.getValue());
			}
		}
		//遍历指定节点下的子节点						
		Iterator iteratoreSon = sonElements.iterator();
		while(iteratoreSon.hasNext()) {
			Element sonElement = (Element)iteratoreSon.next();
			List<Attribute> sonElementAttrs = sonElement.attributes();
			elementName = sonElement.getName();
			if(compareName.equalsIgnoreCase(elementName))
				continue;
			compareName = elementName;
			subElementXml = sonElement.asXML();
			
			//Document sonDoc = new SAXReader().read(new ByteArrayInputStream(subElementXml.getBytes("UTF-8")));
			//当前节点下子节点元素列表
			List<Element>  sonSameElems = doc.selectNodes(xPathExpress+"/"+elementName);
			
			if(sonSameElems.size() > 1) {
				List subList = new ArrayList<>();
				for(Element sonSameElem : sonSameElems) {
					sameElemXml = sonSameElem.asXML();
					subXPathExpress = "/"+elementName;
					subList.add(convertXmlSingleNodeToMap(sameElemXml,subXPathExpress,ignoreAttr));
				}
				nodeDataMap.put(elementName,subList);
			}else {				
				if(sonElement.elements().size() > 0) {//如果子节点下又有子节点
					List subList = new ArrayList<>();
					subList.add(convertXmlSingleNodeToMap(subElementXml,"/"+elementName,ignoreAttr));
					nodeDataMap.put(elementName,subList);
				}else {	
					if(sonElementAttrs.size() > 0) {
						Map sonNodeDataMap = new LinkedHashMap<>();
						Iterator iterSonElemAttrs = sonElementAttrs.iterator();
						while(iterSonElemAttrs.hasNext()) {
							Attribute sonElemAttr = (Attribute) iterSonElemAttrs.next();	
							sonNodeDataMap.put(sonElemAttr.getName(), sonElemAttr.getValue());
						}
						if(sonElement.getText().trim().length() > 0)
							sonNodeDataMap.put("textvalue", sonElement.getText());
						nodeDataMap.put(sonElement.getName(), sonNodeDataMap);
					}
					else {
						nodeDataMap.put(sonElement.getName(), sonElement.getText());	
					}
				}
			}
		}
			
		return nodeDataMap;
	}
	
	/**
	 * 此方法为convertXmlToMapList的一子方法,对之前同名方法的优化
	 * 该方法实现对指定xml节点的单个循环节点下的map转换，并支持递归
	 * @param node xml节点
	 * @param ignoreAttr 是否忽略对属性的解析
	 * @return
	 * @throws Exception
	 */
	public static Map<String,Object> convertXmlSingleNodeToMap(Node node,Boolean ignoreAttr) throws Exception{	
		Map nodeDataMap = new LinkedHashMap<>();
		String elementName="",subElementName ="",compareName="";	
		int elementsCount = 0;
		Element element = null;
		if (node instanceof Element)
			element =  (Element)node;		
		elementName = element.getName();
		elementsCount = element.elements().size();
		//得到当前节点里的属性
		List<Attribute> elementAttrs = element.attributes();
		//得到当前节点下的子节点集合
		List<Element> sonElements = element.elements();
		
		if(!ignoreAttr) {
			//遍历当前节点里的属性
			Iterator iteratoreAttrs = elementAttrs.iterator();
			while(iteratoreAttrs.hasNext()) {
				Attribute attr = (Attribute) iteratoreAttrs.next();
				nodeDataMap.put(attr.getName(),attr.getValue());
			}
		}
		//遍历指定节点下的子节点						
		Iterator iteratoreSon = sonElements.iterator();
		while(iteratoreSon.hasNext()) {
			Element sonElement = (Element)iteratoreSon.next();
			List<Attribute> sonElementAttrs = sonElement.attributes();
			elementName = sonElement.getName();
			if(compareName.equalsIgnoreCase(elementName))
				continue;
			compareName = elementName;
			
			List<Element> sonSameElems = element.selectNodes(elementName);
			if(sonSameElems.size() > 1) {
				List subList = new ArrayList<>();
				for(Element sonSameElem : sonSameElems) {
					subList.add(convertXmlSingleNodeToMap(sonSameElem,ignoreAttr));
				}
				nodeDataMap.put(elementName,subList);
			}else {
				if(sonElement.elements().size() > 0) {//如果子节点下又有子节点
					List subList = new ArrayList<>();
					subList.add(convertXmlSingleNodeToMap(sonElement,ignoreAttr));
					nodeDataMap.put(elementName,subList);
				}else {	
					if(sonElementAttrs.size() > 0) {
						Map sonNodeDataMap = new LinkedHashMap<>();
						Iterator iterSonElemAttrs = sonElementAttrs.iterator();
						while(iterSonElemAttrs.hasNext()) {
							Attribute sonElemAttr = (Attribute) iterSonElemAttrs.next();	
							sonNodeDataMap.put(sonElemAttr.getName(), sonElemAttr.getValue());
						}
						if(sonElement.getText().trim().length() > 0)
							sonNodeDataMap.put("textvalue", sonElement.getText());
						nodeDataMap.put(sonElement.getName(), sonNodeDataMap);
					}
					else {
						nodeDataMap.put(sonElement.getName(), sonElement.getText());	
					}
				}
			}			
		}
			
		return nodeDataMap;
	}

	/**
	 * 把xml文档内容以 Map<String,Object> 形式的List数组返回（支持递归嵌套）<br/>
	 * 友情推荐：在xml文档内容不大时使用该方法是一个不错的选择
	 * @param xmlStr  xml 文档内容
	 * @param xPathExpress xPath 表达式 如果为空则从根节点开始遍历
	 * @return Map<String,Object>形式的List数组
	 * @throws Exception
	 */
	public static List<Map<String,Object>> convertXmlToMapList(String xmlStr,String xPathExpress) throws Exception{
		return convertXmlToMapList(xmlStr,xPathExpress,false);
	}
	
	/**
	 * 把xml文档内容以 Map<String,Object> 形式的List数组返回（支持递归嵌套）<br/>
	 * 友情推荐：在xml文档内容不大时使用该方法是一个不错的选择
	 * @param xmlStr xml 文档内容
	 * @param xPathExpress xPath 表达式 如果为空则从根节点开始遍历
	 * @param ignoreAttr 是否忽略对属性的解析
	 * @return Map<String,Object>形式的List数组
	 * @throws Exception
	 */
	public static List<Map<String,Object>> convertXmlToMapList(String xmlStr,String xPathExpress,Boolean ignoreAttr) throws Exception{
		List list = new ArrayList<Map<String,Object>>();
		Map nodeDataMap = null;
		String subElementXml ="",elementName="";
		
		Document doc = new SAXReader().read(new ByteArrayInputStream(xmlStr.getBytes("UTF-8")));
		
		if("".equals(xPathExpress) || null == xPathExpress)//如果传过来的xPathExpress为空则从根节点开始解析
		{
			 Element  root = doc.getRootElement();//获取根节点
			 xPathExpress = root.getName();
		}
		
		List<Element> elements = doc.selectNodes(xPathExpress);
		
		//遍历指定节点
		Iterator iteratorElement = elements.iterator(); 
		while(iteratorElement.hasNext()) {
			Element element = (Element)iteratorElement.next();
			subElementXml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n" + element.asXML();
			elementName = element.getName();			
			//list.add(convertXmlSingleNodeToMap(subElementXml,xPathExpress,ignoreAttr));
			list.add(convertXmlSingleNodeToMap(element,ignoreAttr));
		}
		System.out.println(list);
		return list;
	}


	/**
	 * java对象转换为xml
	 * @param obj 实体对象
	 * @param targetClazz java对象.Class
	 * @return
	 * @throws JAXBException
	 */
    public static String beanToXml(Object obj,Class<?> targetClazz) throws JAXBException{
        JAXBContext context = JAXBContext.newInstance(targetClazz);
        Marshaller marshaller = context.createMarshaller();
        marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
        marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
        StringWriter writer = new StringWriter();
        marshaller.marshal(obj,writer);
        return writer.toString();
    } 
    
    /**
     * xml转换为java对象
     * @param xml xml字符串
     * @param targetClazz java对象.Class
     * @return
     * @throws Exception
     */
    public static Object xmlToBean(String xml,Class<?> targetClazz) throws Exception{
    	StringReader reader = new StringReader(xml);
        JAXBContext context = JAXBContext.newInstance(targetClazz);  
        Unmarshaller unmarshaller = context.createUnmarshaller(); 
        Object object = unmarshaller.unmarshal(reader);
        return object;
    }
    
    /**
     * xml文件转换为java对象
     * @param xmlPath xml文件路径
     * @param targetClazz java对象.Class
     * @return
     * @throws Exception
     */
    public static Object xmlFileToBean(String xmlPath,Class<?> targetClazz) throws Exception{
        JAXBContext context = JAXBContext.newInstance(targetClazz);  
        Unmarshaller unmarshaller = context.createUnmarshaller(); 
        Object object = unmarshaller.unmarshal(new File(xmlPath));
        return object;
    }
    
    
    /**
     * 用于把xml文档内容通过xslt转换模板文件转换为其他格式的内容时制定的转换类型
     * @author 李启联
     *
     */
    public enum XmlTransFormMethod{
    	xml,
    	html,
    	text;
    }
    
    /**
     * 把xml文档内容通过xslt转换模板文件转换为其他格式的内容
     * @param xml 源xml文档内容
     * @param xsltRelativePath xsl转换文件的相对路径 (例如：xmltransformer/test.xsl)
     * @param method 转换的类型 xml;html;text
     * @return
     */
	public static String xmlToOther(String xml, String xsltRelativePath,XmlTransFormMethod method) {
		try {
			ByteArrayOutputStream byteRep = new ByteArrayOutputStream();
			TransformerFactory transformerFactory = TransformerFactory.newInstance();
			StreamSource source = new StreamSource(new ByteArrayInputStream(xml.getBytes("UTF-8")));
			StreamResult result = new StreamResult(byteRep);
			
			InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream(xsltRelativePath);
			
			//StreamSource style = new StreamSource(xsltFilePath);
			StreamSource style = new StreamSource(stream);
			
			Transformer transformer = transformerFactory.newTransformer(style);
			transformer.setOutputProperty(javax.xml.transform.OutputKeys.METHOD, method.toString());
			transformer.setOutputProperty(javax.xml.transform.OutputKeys.ENCODING, "UTF-8");
			transformer.transform(source, result);
			return byteRep.toString();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	
	/**
	 * 检查判断Map里的EntrySet的值是否全是List类型的对象
	 * @param map 要检查判断的Map
	 * @return 
	 */
	private static boolean checkMapEntrySetIsAllListObj(Map<String,Object> map) {
		boolean isAllList = true;
		Object keyValue = null;
		for(Map.Entry<String,Object> col : map.entrySet()) {
			keyValue = col.getValue();
			if(!(keyValue instanceof List)) {
				isAllList = false;
				break;
			}
		}	
		return isAllList;
	}


	public static String generateXmlFromList(List<Map<String, Object>> rows,String elementName, String[] groupElementName,String[] ignoreColumn) {
		StringBuilder sb = new StringBuilder();
		String indent ="";//缩进
		
		for(String str : groupElementName) {
			sb.append("<"+str+">").append("\n").append(" ");
		}
		for (Map<String, Object> row : rows) {
			sb.append(" <"+elementName+">").append("\t").append("\n");
			List<Map.Entry<String, Object>> columnList = new ArrayList<Map.Entry<String, Object>>(row.entrySet());		
			for (Map.Entry<String, Object> column : columnList) {
				if(!isInIgnoreColumn(ignoreColumn,column.getKey()) && null != column.getValue()) {
					sb.append("	<" + column.getKey() + ">" + column.getValue().toString() + "</"+ column.getKey() + ">").append("\n");
				}
			}
			sb.append(" </"+elementName+">").append("\t").append("\n");
						
		}
		for(int i = groupElementName.length - 1; i >=0 ;i-- ) {
			indent ="";
			for(int j =0 ; j < i;j++) {
				indent +=" ";
			}				
			sb.append(indent).append("</"+groupElementName[i]+">").append("\n");
		}
		return sb.toString();
	}
	
	/**
	 * 判断一指定名称是否在忽略的数组里
	 * @param ignoreColumn 忽略的字符数组
	 * @param colName 判断的名称
	 * @return
	 */
	protected static boolean isInIgnoreColumn(String[] ignoreColumn,String colName) {
		if(ignoreColumn.length < 1)
			return false;
		
		for(String col : ignoreColumn) {
			if(colName.trim().equalsIgnoreCase(col)) {
				return true;
			}
		}		
		return false;
	}

}
